# --coding:utf-8--

# Copyright 2021 xpstem.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from lelink.conn.wificonnection import *
from lelink.ev3device import *
from lelink.eventbus import EventBus
from lelink.ev3operation import *


class EV3BrickClient(EventBus):
    __slots__ = ("layer",
                 "_connection",
                 "_inputdevice_map",
                 "_outputdevice_map",
                 "_supportdevice_map")

    @staticmethod
    def _class_meta(module_name, class_name):
        module_meta = __import__(module_name, globals(), locals(), [class_name])
        return getattr(module_meta, class_name)

    def __init__(self, layer: Layer):
        self.layer = layer
        self._connection = None
        self._outputdevice_map = {}
        self._inputdevice_map = {}
        self._supportdevice_map = {
            EV3ColorSensor.TYPE: self._class_meta('lelink.ev3device', 'EV3ColorSensor'),
            EV3IRSensor.TYPE: self._class_meta('lelink.ev3device', 'EV3IRSensor'),
            EV3UltrasonicSensor.TYPE: self._class_meta('lelink.ev3device', 'EV3UltrasonicSensor'),
            EV3LargeMotor.TYPE: self._class_meta('lelink.ev3device', 'EV3LargeMotor'),
            EV3MediumMotor.TYPE: self._class_meta('lelink.ev3device', 'EV3MediumMotor'),
        }

    def get_outputdevice(self, port: OutputPort) -> EV3OutputDevice:
        if port in self._outputdevice_map.keys():
            return self._outputdevice_map.get(port)
        return None

    def get_inputdevice(self, port: InputPort) -> EV3InputDevice:
        if port in self._inputdevice_map.keys():
            return self._inputdevice_map.get(port)
        return None

    def connect(self, conn: BaseConnection) -> None:
        self._connection = conn
        self._list_devices()

    def post_command(self, cmd: EV3Command, callback=None) -> None:
        self.execute_command(cmd, callback)

    def refresh(self) -> None:
        self._list_devices()

    def execute_command(self, command: EV3Command, callback=None) -> None:
        self._connection.send(command, callback)

    def _list_devices(self) -> None:
        num_of_val = 8
        array_param = ReturnParam(0, Data8(0), [Data8(0)] * num_of_val)
        changed_param = ReturnParam(num_of_val, Data8(0))
        list_op = EV3InputOpFactory.device_list(Data8(num_of_val), array_param, changed_param)
        cmd = EV3DirectCommand({list_op})
        self.execute_command(cmd)

        for port_no in range(0, len(array_param.value_array)):
            dev_type = array_param.value_array[port_no].value
            if dev_type == EV3InputOpFactory.PORT_EMPTY_OR_UNAVAILABLE:
                continue

            if dev_type in self._supportdevice_map.keys():
                in_port = InputPort(port_no)

                type_param = ReturnParam(0, Data8(0))
                mode_param = ReturnParam(1, Data8(1))
                op = EV3InputOpFactory.OpInputDevice.get_typemode(self.layer, in_port, type_param, mode_param)
                cmd = EV3DirectCommand({op})
                self.execute_command(cmd)

                if dev_type != type_param.value.value:
                    raise IOError("wrong device type.")

                clazz = self._supportdevice_map.get(dev_type)
                in_device = clazz(self, in_port, mode_param.value.value)
                self._inputdevice_map[in_port] = in_device

        for out_port in output_ports.keys():
            type_param = ReturnParam(0, Data8(0))
            mode_param = ReturnParam(1, Data8(1))
            op = EV3InputOpFactory.OpInputDevice.get_typemode(self.layer, output_ports[out_port], type_param,
                                                              mode_param)
            cmd = EV3DirectCommand({op})
            self.execute_command(cmd)

            dev_type = type_param.value.value
            if dev_type == EV3InputOpFactory.PORT_EMPTY_OR_UNAVAILABLE:
                continue

            clazz = self._supportdevice_map.get(dev_type)
            out_device = clazz(self, out_port, mode_param.value.value)
            self._outputdevice_map[out_port] = out_device
            self._inputdevice_map[output_ports[out_port]] = out_device.sensor
